package roborally.program.text;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import be.kuleuven.cs.som.annotate.Basic;

import roborally.program.CommandManager;
import roborally.program.command.BasicCondition;
import roborally.program.command.Command;
import roborally.program.command.ComplexCondition;
import roborally.program.command.Condition;
import roborally.program.command.IfCommand;
import roborally.program.command.WhileCommand;
import roborally.program.command.ComplexCondition.ConditionCombinationProcedure;
import roborally.program.command.ExecutionCommand;
import roborally.program.command.SequenceCommand;
import roborally.program.command.BasicCondition.ConditionProcedure;
import roborally.program.command.ExecutionCommand.ExecutionProcedure;

//We gaan er van uit dat en commando woord steeds met een haakje begint en eindigt.
public enum TextFactory {
	
	EXECUTION{

		private Map<String, ExecutionProcedure> patterns;
		
		@Override
		public void setUp() {
			this.setSetUp();
			patterns = new HashMap<String, ExecutionProcedure>();
			for(ExecutionProcedure lusProc : ExecutionProcedure.values())
				patterns.put(lusProc.getStringPattern(), lusProc);
		}
		
		@Override
		public TextCommandPattern getPattern() {
			assert this.getSetUp();
			
			return new TextCommandPattern() {
				
				@Override
				public boolean matches(String word) {
					return TextFactory.EXECUTION.matches(word, patterns);
				}
				
				@Override
				public Command create(String word) {
					for(String pattern : patterns.keySet())
						if(Pattern.matches(pattern, word)) {
							ExecutionProcedure proc = patterns.get(pattern);
							return new ExecutionCommand(proc, TextFactory.EXECUTION.getArguments(proc, word));
						}
					throw new IllegalArgumentException("Word is not valid.\n" + word);
				}
			};
		}
	},
	SEQUENCE {

		private String pattern;
		
		@Override
		public void setUp() {
			this.setSetUp();
			pattern = "^(\\()(\\s*)(" + SequenceCommand.getWord() + ")(.*)(\\))";	
		}
		
		@Override
		public TextCommandPattern getPattern() {
			assert getSetUp();
			return new TextCommandPattern() {
				
				//Enkel controle of aan het sequentie patroon voldaan is. Niet of er binnen de sequentie
				//een geldig patroon staat. Anders als we dit recursief doen, controleren we dat zeer veel.
				//Daarvoor moet er nog een parser komen die dat enkel doet.
				@Override
				public boolean matches(String word) {
					return Pattern.matches(pattern, word);
				}
				
				@Override
				public Command create(String word) throws IllegalArgumentException {
					if(matches(word)) {
						SequenceCommand sc = new SequenceCommand();
						word = word.replaceFirst("^(\\()(\\s*)(" + SequenceCommand.getWord() + ")", "");
						word = word.substring(0, word.length() - 1).trim();
						sc.addAsCommands(CommandManager.get().create(word));
						return sc;
					}
					throw new IllegalArgumentException();
				}
			};
		}

	},
	BASIC_CONDITION {

		private Map<String, ConditionProcedure> patterns;
		
		@Override
		public void setUp() {
			this.setSetUp();
			patterns = new HashMap<String, ConditionProcedure>();
			for(ConditionProcedure lusProc : ConditionProcedure.values())
				patterns.put(lusProc.getStringPattern(), lusProc);			
		}

		@Override
		public TextCommandPattern getPattern() {
			assert getSetUp();
			return new TextCommandPattern() {
				
				@Override
				public boolean matches(String word) {
					return TextFactory.BASIC_CONDITION.matches(word, patterns);
				}
				
				@Override
				public Command create(String word) throws IllegalArgumentException {
					for(String pattern : patterns.keySet())
						if(Pattern.matches(pattern, word)) {
							word = word.replaceFirst("^(\\()(\\s*)([\\w]+)", "");
							word = word.substring(0, word.length() - 1);
							ConditionProcedure proc = patterns.get(pattern);
							return new BasicCondition(proc, TextFactory.BASIC_CONDITION.getArguments(proc, word));
						}
					throw new IllegalArgumentException("Unable to create a basic condition with the given word: " + word);
				}
			};
		}
		
	}, COMPLEX_CONDITION {

		private Map<String, ConditionCombinationProcedure> patterns;
		
		@Override
		public void setUp() {
			this.setSetUp();
			patterns = new HashMap<String, ConditionCombinationProcedure>();
			for(ConditionCombinationProcedure lusProc : ConditionCombinationProcedure.values())
				patterns.put(lusProc.getStringPattern(), lusProc);
		}
		
		@Override
		public TextCommandPattern getPattern() {
			assert getSetUp();
			
			return new TextCommandPattern() {
				
				@Override
				public boolean matches(String word) {
					return TextFactory.COMPLEX_CONDITION.matches(word, patterns);
				}
				
				@Override
				public Command create(String word) {
					for(String pattern : patterns.keySet())
						if(Pattern.matches(pattern, word)) {
							ConditionCombinationProcedure proc = patterns.get(pattern);
							word = word.replaceFirst("^(\\()(\\s*)(" + proc.toString() + ")", "");
							word = word.substring(0, word.length() - 1).trim();
							List<String> subBlocks = CommandManager.get().getBlocks(word);
							List<Object> args = new ArrayList<Object>();
							for(String lusBlock : subBlocks)
								args.add(proc.getValue(lusBlock));
							return new ComplexCondition(proc, args.toArray());
						}
					throw new IllegalArgumentException("Unable to create a complex condition with the given word: " + word);
				}
			};
		}

		
	}, WHILE {

		private String pattern;
		
		@Override
		public void setUp() {
			this.setSetUp();
			pattern = "^(\\()(\\s*)(" + WhileCommand.getWord() + ")(\\s*)" +
					"(" +
						//eventueel wit karakter(s) - haakje - vervolgens: of eventueel wit karakter(s) - haakje - willekeurige karakters
						//haakje - eventueel wit karakter(s) of wikkekeurige karakters maar geen haakje.
						"(\\s*)(\\()" + "(((\\s*)([^\\(\\)]+)(\\()(.*)(\\))(\\s*))|([^\\(\\)]*))" + "(\\s*)(\\))(\\s*)" +
					//geeft aan een herhaling van 2 keer.
					"){2}" +
					"(\\s*)(\\))";
		}
		
		@Override
		public TextCommandPattern getPattern() {
			assert getSetUp();
			
			return new TextCommandPattern() {
				
				@Override
				public boolean matches(String word) {
					return Pattern.matches(pattern, word);
				}
				
				@Override
				public Command create(String word) {
					word = word.replaceFirst("^(\\()(\\s*)(" + WhileCommand.getWord() + ")", "");
					word = word.substring(0, word.length() - 1).trim();
					List<String> blocks = CommandManager.get().getBlocks(word);
					if(blocks.size() == 2) {
						Condition condition = Condition.class.cast(CommandManager.get().createCommand(blocks.get(0)));
						WhileCommand whileCommand = new WhileCommand(condition);
						whileCommand.addAsCommands(CommandManager.get().create(blocks.get(1)));
						return whileCommand;
					}
					throw new IllegalArgumentException("Could not create a while command with the given word: " + word);
				}
			};
		}
		
	}, IF {

		private String pattern;
		
		@Override
		public void setUp() {
			this.setSetUp();
			pattern = "^(\\()(\\s*)(" + IfCommand.getWord() + ")(\\s*)" +
					"(" +
						//eventueel wit karakter(s) - haakje - vervolgens: of eventueel wit karakter(s) - haakje - willekeurige karakters
						//haakje - eventueel wit karakter(s) of wikkekeurige karakters maar geen haakje.
						"(\\s*)(\\()" + "(((\\s*)([^\\(\\)]+)(\\()(.*)(\\))(\\s*))|([^\\(\\)]*))" + "(\\s*)(\\))(\\s*)" +
					//geeft aan een herhaling van 2 keer.
					"){3}" +
					"(\\s*)(\\))";
		}
		
		@Override
		public TextCommandPattern getPattern() {
			assert this.getSetUp();
			
			return new TextCommandPattern() {
				
				@Override
				public boolean matches(String word) {
					return Pattern.matches(pattern, word);
				}
				
				@Override
				public Command create(String word) {
					word = word.replaceFirst("^(\\()(\\s*)(" + IfCommand.getWord() + ")", "");
					word = word.substring(0, word.length() - 1).trim();
					List<String> blocks = CommandManager.get().getBlocks(word);
					if(blocks.size() == 3) {
						Condition condition = Condition.class.cast(CommandManager.get().createCommand(blocks.get(0)));
						IfCommand ifCommand = new IfCommand(condition);
						ifCommand.addAsCommands(CommandManager.get().create(blocks.get(1)));
						ifCommand.addAsFalseCommands(CommandManager.get().create(blocks.get(2)));
						return ifCommand;
					}
					throw new IllegalArgumentException("Could not create a while command with the given word: " + word);
				}
			};
		}

		
		
	};
	
	@Basic 
	public boolean getSetUp() {
		return this.isSetUp;
	}

	protected void setSetUp() {
		this.isSetUp = true;
	}
	
	private boolean isSetUp = false;
	
	private TextFactory() {
		this.setUp();
	}
		
	public abstract void setUp();
	
	public abstract TextCommandPattern getPattern();

	private boolean matches(String word, Map<String, ?> patterns) {
		for(String pattern : patterns.keySet())
			if(Pattern.matches(pattern, word))
				return true;
		return false;
	}
	
	private Object[] getArguments(TextProcedure proc,
			String word) {
		List<Object> args = new ArrayList<Object>();
		String[] subword = word.replaceAll("\\(", " ").replaceAll("\\)"," ").trim().split("\\s++");
		for(int i = 1 ; i < subword.length; i++)
			args.add(proc.getValue(subword[i]));
		return args.toArray();
	}

}
